/*	$Id: thread.c 1118 2010-12-12 21:05:32Z phrakt $	*/
/*
 * Copyright (c) 2006 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/time.h>

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include <canopy/log.h>
#include <canopy/mem.h>
#include <canopy/queue.h>
#include <canopy/string.h>

#include "thread.h"
#include "private.h"



static pthread_key_t    cnp_thr_key;



static cnp_thr_t* cnp_thr_alloc (const char *, void * (*)(void *), void *);
static void       cnp_thr_free  (void *);
static void*      cnp_thr_main  (void *);


/*
 * cnp_thr_init()
 *
 * Initialize the thread library.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_thr_init(void)
{
	int ret;
	cnp_thr_t *self;

	ret = pthread_key_create(&cnp_thr_key, cnp_thr_free);
	if (ret != 0)
		return (-1);

	if ((self = cnp_thr_alloc("main", NULL, NULL)) == NULL) {
		(void)pthread_key_delete(cnp_thr_key);
		return (-1);
	}

	if (pthread_setspecific(cnp_thr_key, self) != 0) {
		(void)pthread_key_delete(cnp_thr_key);
		cnp_thr_free(self);
		return (-1);
	}

	return (0);
}

/*
 * cnp_thr_cleanup()
 *
 * Release any resources allocated by the thread library.
 */
void
cnp_thr_cleanup(void)
{
	(void)pthread_key_delete(cnp_thr_key);
}

/*
 * cnp_thr_create()
 *
 * Create a new thread named <name> and add it to the pool.  Upon creation,
 * the thread will execute the function <thr_main>, with <arg> as the argument
 * passed to that function.
 *
 * Returns a thread ID on success, or -1 on failure.
 */
cnp_thr_t*
cnp_thr_create(const char *name, void * (*thr_main)(void *), void *arg)
{
	int ret;
	cnp_thr_t *thr;

	if ((thr = cnp_thr_alloc(name, thr_main, arg)) == NULL)
		return (NULL);

	ret = pthread_create(&thr->thr_hdl, NULL, cnp_thr_main, thr);
	if (ret != 0) {
		cnp_log_err(cnp_logchan, "failed to create thread");
		cnp_free(thr);
		return (NULL);
	}

	return (thr);
}

/*
 * cnp_thr_cancel()
 *
 * Request the cancellation of the thread whose handle is <thread>.  As
 * cancellation is an asynchronous operation, there is no guarantee that
 * cancellation will have successfully occurred even if this function returns
 * success.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_thr_cancel(cnp_thr_t *thread)
{
	int ret;

	ret = pthread_cancel(thread->thr_hdl);
	if (ret != 0) {
		if (ret == ESRCH)
			cnp_error_set(CNP_ERR_INVTHR);
		ret = -1;
	}

	return (ret);
}

/*
 * cnp_thr_detach()
 *
 * Detach the thread whose handle is <thread>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_thr_detach(cnp_thr_t *thread)
{
	int ret;

	ret = pthread_detach(thread->thr_hdl);
	if (ret != 0) {
		if (ret == ESRCH)
			cnp_error_set(CNP_ERR_INVTHR);
		ret = -1;
	}

	return (ret);
}

/*
 * cnp_thr_exit()
 *
 * Terminate the calling thread and use <ret> as the value made available
 * for a call to cnp_thr_join().
 *
 * This function does not return.
 */
void
cnp_thr_exit(void *ret)
{
	pthread_exit(ret);
}

/*
 * cnp_thr_join()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_thr_join(cnp_thr_t *thread, void **vptr)
{
	int ret;

	ret = pthread_join(thread->thr_hdl, vptr);
	if (ret != 0) {
		if (ret == ESRCH)
			cnp_error_set(CNP_ERR_INVTHR);
		else if (ret == EDEADLK)
			cnp_error_set(CNP_ERR_DEADLOCK);
		ret = -1;
	}

	return (ret);
}

/*
 * cnp_thr_self()
 *
 * Returns the thread handle of the calling thread.
 */
cnp_thr_t*
cnp_thr_self(void)
{
	return (cnp_thr_t *)pthread_getspecific(cnp_thr_key);
}

/*
 * cnp_thr_getname()
 *
 * Copy the name of the thread whose handle is <thread> into the destination
 * buffer <buf>, which is of length <len>.
 *
 * Returns the length of the string that the function tried to copy, or -1
 * if an error other than truncation occurred.  If the return value is equal
 * to or larger than <len>, the buffer <dst> was too small and the name
 * was truncated.
 */
ssize_t
cnp_thr_getname(cnp_thr_t *thread, char *buf, size_t len)
{
	size_t ret;

	if (cnp_rwlock_rdlock(&thread->thr_lock) == -1)
		return (-1);

	ret = strlcpy(buf, thread->thr_name, len);

	(void)cnp_rwlock_unlock(&thread->thr_lock);

	return (ssize_t)ret;
}

/*
 * cnp_thr_setname()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_thr_setname(cnp_thr_t *thread, const char *fmt, ...)
{
	va_list vap;

	if (cnp_rwlock_wrlock(&thread->thr_lock) == -1)
		return (-1);

	va_start(vap, fmt);
	vsnprintf(thread->thr_name, sizeof(thread->thr_name), fmt, vap);
	va_end(vap);

	(void)cnp_rwlock_unlock(&thread->thr_lock);

	return (0);
}

/*
 * cnp_thr_sigmask()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_thr_sigmask(int how, const sigset_t *set, sigset_t *oset)
{
	int ret;

	ret = pthread_sigmask(how, set, oset);
	if (ret != 0) {
		ret = -1;
	}

	return (ret);
}

/*
 * cnp_thr_signal()
 *
 * Kill the thread whose handle is specified in <thrid>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_thr_signal(cnp_thr_t *thread, int sig)
{
	int ret = 0;

	ret = pthread_kill(thread->thr_hdl, SIGTERM);
	if (ret != 0) {
		cnp_log_err(cnp_logchan, "failed to signal thread");
		if (ret == ESRCH)
			cnp_error_set(CNP_ERR_INVTHR);
		ret = -1;
	}

	return (ret);
}

/*
 * cnp_thr_keycreate()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_thr_keycreate(cnp_thrkey_t *keyptr, void (*destructor)(void *))
{
	int ret;

	ret = pthread_key_create(&keyptr->ct_key, destructor);
	if (ret != 0) {
		if (ret == ENOMEM)
			cnp_error_set(CNP_ERR_NOMEM);
		ret = -1;
	}

	return (ret);
}

/*
 * cnp_thr_keydelete()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_thr_keydelete(cnp_thrkey_t key)
{
	int ret;

	ret = pthread_key_delete(key.ct_key);
	if (ret != 0) {
		ret = -1;
	}

	return (ret);
}

/*
 * cnp_thr_getspecific()
 *
 */
void*
cnp_thr_getspecific(cnp_thrkey_t key)
{
	return pthread_getspecific(key.ct_key);
}

/*
 * cnp_thr_setspecific()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_thr_setspecific(cnp_thrkey_t key, void *data)
{
	int ret;

	ret = pthread_setspecific(key.ct_key, data);
	if (ret != 0) {
		if (ret == ENOMEM)
			cnp_error_set(CNP_ERR_NOMEM);
		ret = -1;
	}

	return (ret);
}

/*
 * cnp_thr_alloc()
 *
 * Allocate a new thread structure
 *
 * Returns a pointer to the new thread structure on success, or NULL on failure.
 */
static cnp_thr_t*
cnp_thr_alloc(const char *name, void * (*thr_main)(void *), void *arg)
{
	cnp_thr_t *thr;

	if ((thr = cnp_malloc(sizeof(*thr))) == NULL) {
		cnp_log_errno(cnp_logchan, "failed to allocate thread");
		return (NULL);
	}

	memset(thr, 0, sizeof(*thr));

	if (cnp_rwlock_init(&thr->thr_lock, NULL) == -1) {
		cnp_log_err(cnp_logchan, "failed to initialize thread lock");
		cnp_free(thr);
		return (NULL);
	}

	strlcpy(thr->thr_name, name, sizeof(thr->thr_name));

	thr->thr_main = thr_main;
	thr->thr_arg = arg;

	return (thr);
}

/*
 * cnp_thr_free()
 *
 */
static void
cnp_thr_free(void *thread)
{
	cnp_thr_t *thr = (cnp_thr_t *)thread;

	(void)cnp_rwlock_destroy(&thr->thr_lock);
	cnp_free(thr);
}

/*
 * cnp_thr_main()
 *
 * Wrapper function used as the actual routine executed by the thread.
 */
static void*
cnp_thr_main(void *arg)
{
	void *ret = NULL;
	cnp_thr_t *self = (cnp_thr_t *)arg;

	pthread_setspecific(cnp_thr_key, arg);

	ret = (*self->thr_main)(self->thr_arg);

	return (ret);
}
